#include <iostream>
#include <sstream>
#include "Board.h"

const string Board::LINE_TEXTS[] = { "１", "２", "３", "４", "５", "６", "７", "８" };
const int Board::POSITION_POINT[BOARD_SIZE][BOARD_SIZE] = { { 120, -24, 0, -1, -1, 0, -24, 120 }, { -24, -60, -3, -3, -3, -3, -60, -24 }, { 0, -3, 0, -1, -1, 0,
		-3, 0 }, { -1, -3, 0, -1, -1, 0, -3, -1 }, { -1, -3, 0, -1, -1, 0, -3, -1 }, { 0, -3, 0, -1, -1, 0, -3, 0 }, { -24, -60, -3, -3, -3, -3, -60, -24 }, {
		120, -24, 0, -1, -1, 0, -24, 120 } };
const int Board::PUTABLE_MAP[] = { -50, -30, -10, -7, -4, -1, 0, 1, 2 };

Board::Board() :
		mTurnCount(0), mStoneNum(0), mState(NULL), mCells(NULL), mCompleted(false) {
	mPutableCellsCache[0] = NULL;
	mPutableCellsCache[1] = NULL;
	mCells = new Cell**[BOARD_SIZE];
	for (int x = 0; x < BOARD_SIZE; x++) {
		mCells[x] = new Cell*[BOARD_SIZE];
		for (int y = 0; y < BOARD_SIZE; y++) {
			mCells[x][y] = new Cell(x, y);
		}
	}

//	if (initialize) {
	init();
//	}
}

Board::Board(const Board& board) :
		mTurnCount(0), mStoneNum(0), mState(NULL), mCells(NULL), mCompleted(false) {
	mTurnCount = board.mTurnCount;
	mStoneNum = board.mStoneNum;
	mCompleted = board.mCompleted;
	mPutableCellsCache[0] = NULL;
	mPutableCellsCache[1] = NULL;

	mCells = new Cell**[BOARD_SIZE];
	for (int x = 0; x < BOARD_SIZE; x++) {
		mCells[x] = new Cell*[BOARD_SIZE];
		for (int y = 0; y < BOARD_SIZE; y++) {
			mCells[x][y] = new Cell(*board.mCells[x][y]);
		}
	}
}

Board::~Board() {
	for (int x = 0; x < BOARD_SIZE; x++) {
		for (int y = 0; y < BOARD_SIZE; y++) {
			delete mCells[x][y];
		}
	}
	for (int x = 0; x < BOARD_SIZE; x++) {
		delete mCells[x];
	}
	delete mCells;
	delete mState;
	mCells = NULL;
	mState = NULL;
	delete mPutableCellsCache[0];
	delete mPutableCellsCache[1];
}

void Board::init() {
	if (BOARD_SIZE % 2 == 0) {
		int harf = BOARD_SIZE / 2;
		mCells[harf - 1][harf - 1]->setStone(Stone::WHITE);
		mCells[harf][harf - 1]->setStone(Stone::BLACK);
		mCells[harf - 1][harf]->setStone(Stone::BLACK);
		mCells[harf][harf]->setStone(Stone::WHITE);
		mStoneNum = 4;
	}
}

Board::State* Board::getState() {
	if (mState != NULL) {
		delete mState;
		mState = NULL;
	}
	mState = new Board::State(this);
	return mState;
}

Cell* Board::getCell(int x, int y) {
	return mCells[x][y];
}

bool Board::isFull() {
	return mStoneNum >= (BOARD_SIZE * BOARD_SIZE);
}

int Board::getTurnCount() {
	return mTurnCount;
}

int Board::getStoneCount() {
	return mStoneNum;
}

void Board::setStone(int x, int y, int color) {
	putStone(mCells[x][y], color);
}

void Board::putStone(Cell* cell, int color) {
	mTurnCount++;
	if (mState) {
		delete mState;
		mState = NULL;
	}
	if (cell == NULL) {
		return;
	}
	putStoneWithTurn(cell, 1, 0, color);
	putStoneWithTurn(cell, -1, 0, color);
	putStoneWithTurn(cell, 0, 1, color);
	putStoneWithTurn(cell, 0, -1, color);
	putStoneWithTurn(cell, 1, 1, color);
	putStoneWithTurn(cell, -1, 1, color);
	putStoneWithTurn(cell, 1, -1, color);
	putStoneWithTurn(cell, -1, -1, color);
	cell->setStone(color);
	mStoneNum++;
	delete mPutableCellsCache[0];
	delete mPutableCellsCache[1];
	mPutableCellsCache[0] = NULL;
	mPutableCellsCache[1] = NULL;
}

void Board::putStoneWithTurn(Cell* cell, int stepX, int stepY, int color) {
	vector<Stone*> stones = findTurnable(cell, stepX, stepY, color);
//	for (int i = 0; i < stones.size(); i++) {
//		stones[i]->turn();
//	}

	vector<Stone*>::iterator it = stones.begin();
	while (it != stones.end()) {
		(*it)->turn();
		++it;
	}
}

vector<Stone*> Board::findTurnable(Cell* cell, int stepX, int stepY, int color) {
	int x = cell->mX;
	int y = cell->mY;
	vector<Stone*> emptyStone(0);
	vector<Stone*> stones(0);
	for (int tx = x + stepX, ty = y + stepY; tx < BOARD_SIZE && ty < BOARD_SIZE && tx >= 0 && ty >= 0; tx += stepX, ty += stepY) {
		Stone* stone = mCells[tx][ty]->mStone;
		if (stone == NULL) {
			return emptyStone;
		} else if (stone->mColor != color) {
			stones.push_back(stone);
		} else if (!stones.empty()) {
			return stones;
		} else {
			return emptyStone;
		}
	}
	return emptyStone;
}

vector<Cell*>* Board::findPutableCells(int color) {
	if (mPutableCellsCache[color] != NULL) {
		return mPutableCellsCache[color];
	}
	vector<Cell*>* cells = new vector<Cell*>();
	for (int x = 0; x < BOARD_SIZE; x++) {
		for (int y = 0; y < BOARD_SIZE; y++) {
			if (mCells[x][y]->mStone == NULL && canPut(x, y, color)) {
				cells->push_back(mCells[x][y]);
			}
		}
	}
	mPutableCellsCache[color] = cells;
	return cells;
}

bool Board::canPut(int x, int y, int color) {
	bool can = canPut(x, y, 1, 0, color) || canPut(x, y, -1, 0, color) || canPut(x, y, 0, 1, color) || canPut(x, y, 0, -1, color) || canPut(x, y, 1, 1, color)
			|| canPut(x, y, -1, 1, color) || canPut(x, y, 1, -1, color) || canPut(x, y, -1, -1, color);
	return can;
}

bool Board::canPut(int x, int y, int stepX, int stepY, int color) {
	bool exsistsOther = false;
	for (int tx = x + stepX, ty = y + stepY; tx < BOARD_SIZE && ty < BOARD_SIZE && tx >= 0 && ty >= 0; tx += stepX, ty += stepY) {
		Stone* stone = mCells[tx][ty]->mStone;
		if (stone == NULL) {
			return false;
		} else if (stone->mColor != color) {
			exsistsOther = true;
		} else if (exsistsOther) {
			return true;
		} else {
			return false;
		}
	}
	return false;
}

bool Board::exisitsStone(int x, int y) {
	return mCells[x][y]->mStone != NULL;
}

bool Board::isImmutable(int color, int x, int y) {
	return (isRoundImmutable(color, x - 1, y - 0) || isRoundImmutable(color, x + 1, y + 0))
			&& (isRoundImmutable(color, x - 0, y - 1) || isRoundImmutable(color, x + 0, y + 1))
			&& (isRoundImmutable(color, x - 1, y - 1) || isRoundImmutable(color, x + 1, y + 1))
			&& (isRoundImmutable(color, x - 1, y + 1) || isRoundImmutable(color, x + 1, y - 1));
}

bool Board::isRoundImmutable(int color, int x, int y) {
	if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) {
		return true;
	}
	Cell* cell = mCells[x][y];
	if (cell->mStone == NULL) {
		return false;
	}
	return cell->mStone->mImmutable && cell->mStone->mColor == color;
}

bool Board::isImmutable(int x, int y) {
	return isRoundImmutable(x - 1, y - 0) && isRoundImmutable(x + 1, y + 0) && isRoundImmutable(x - 0, y - 1) && isRoundImmutable(x + 0, y + 1)
			&& isRoundImmutable(x - 1, y - 1) && isRoundImmutable(x + 1, y + 1) && isRoundImmutable(x - 1, y + 1) && isRoundImmutable(x + 1, y - 1);
}

bool Board::isRoundImmutable(int x, int y) {
	if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) {
		return true;
	}
	Cell* cell = mCells[x][y];
	if (cell->mStone == NULL) {
		return false;
	}
	return cell->mStone->mImmutable;
}

int Board::getEmptyCellCount() {
	return BOARD_SIZE * BOARD_SIZE - mStoneNum;
}

string Board::toString() {
	Board::State* state = getState();
	string sb;
	sb.append("　");
	for (int x = 0; x < BOARD_SIZE; x++) {
		sb.append(LINE_TEXTS[x]);
	}
	sb.append("\n");

	int color = mTurnCount % 2 == 0 ? Stone::BLACK : Stone::WHITE;
	vector<Cell*>* putableCells = findPutableCells(color);

	for (int y = 0; y < BOARD_SIZE; y++) {
		sb.append(LINE_TEXTS[y]);
		for (int x = 0; x < BOARD_SIZE; x++) {
			Cell* cell = mCells[x][y];
			if (cell->mStone != NULL) {
				sb.append(cell->toString());
			} else {
				bool find = false;

				vector<Cell*>::iterator it = putableCells->begin();
				while (it != putableCells->end()) {
					Cell* putableCell = (*it);
					if (putableCell == cell) {
						sb.append("・");
						find = true;
					}
					++it;
				}
				if (!find) {
					sb.append("　");
				}
			}

		}
		sb.append(LINE_TEXTS[y]);
		sb.append("\n");
	}

	sb.append("　");
	for (int x = 0; x < BOARD_SIZE; x++) {
		sb.append(LINE_TEXTS[x]);
	}
	sb.append("\n");
	sb.append(state->toString());
	sb.append("\nPUT:");
	vector<Cell*>::iterator it = putableCells->begin();
	while (it != putableCells->end()) {
		Cell* putableCell = (*it);
		sb.append(putableCell->toStringData());
		++it;
	}
	sb.append("\n");
	return sb;
}

Board::SideState::SideState() :
		mNestNum(0), mEmptyNum(0) {

}

bool Board::SideState::cantTurn() {
	return mEmptyNum == 0 || mNestNum >= mEmptyNum;
}

bool Board::SideState::canPutOther() {
	return mEmptyNum != 0 && mNestNum < mEmptyNum;
}

bool Board::SideState::exsistOther() {
	return mNestNum > 0;
}

bool Board::SideState::canExsistOther() {
	return canPutOther() || exsistOther();
}

bool Board::SideState::isFull() {
	return mEmptyNum == 0;
}

int Board::SideState::getWeight() {
	if (mEmptyNum == 0) {
		return 5;
	}
	return mNestNum;
}

Board::State::State(Board* board) :
		mCountBlack(0), mCountWhite(0), mPositionBlack(0), mPositionWhite(0), mImmutableBlack(0), mImmutableWhite(0), mPotentialImmutableBlack(0), mPotentialImmutableWhite(
				0), mPutablePoint(0), mImmutablePoint(0), mPotentialImmutablePoint(0), mPositionPoint(0), mPoint(0) {
	mPutableCellsCount[0] = 0;
	mPutableCellsCount[1] = 0;

	mBoard = board;
	calculate();
	summary();

	int larger = board->mStoneNum + 1;
	int smaller = BOARD_SIZE * BOARD_SIZE - board->mStoneNum + 1;
	int average = BOARD_SIZE * BOARD_SIZE / 2 + 1;
	mPutablePoint = (calcPutablePoint(mPutableCellsCount[Stone::BLACK]) - calcPutablePoint(mPutableCellsCount[Stone::WHITE])) * PUTTABLE_RATIO * average;
	mImmutablePoint = (mImmutableBlack - mImmutableWhite) * IMMUTABLE_RATIO * larger;
	mPotentialImmutablePoint = (mPotentialImmutableBlack - mPotentialImmutableWhite) * POTEANTIAL_IMMUTABLE_RATIO * larger;
	mPositionPoint = (mPositionBlack - mPositionWhite) * POSITION_RATIO * smaller;
	mPoint = mPutablePoint + mImmutablePoint + mPotentialImmutablePoint + mPositionPoint;
}

int Board::State::calcPutablePoint(int num) {
	if (num >= 9) {
		return 3;
	}
	return Board::PUTABLE_MAP[num];
}

void Board::State::calculate() {
	mPutableCellsCount[Stone::BLACK] = mBoard->findPutableCells(Stone::BLACK)->size();
	mPutableCellsCount[Stone::WHITE] = mBoard->findPutableCells(Stone::WHITE)->size();

	if (mBoard->mCells[0][0]->mStone == NULL && mBoard->mCells[0][BOARD_SIZE - 1]->mStone == NULL && mBoard->mCells[BOARD_SIZE - 1][0]->mStone == NULL
			&& mBoard->mCells[BOARD_SIZE - 1][BOARD_SIZE - 1]->mStone == NULL) {
		return;
	}

	int half = BOARD_SIZE / 2;
	for (int depth = 0; depth < half; depth++) {
		int s = depth;
		int e = BOARD_SIZE - depth - 1;
		int l = BOARD_SIZE - (depth * 2) - 1;
		bool found = false;
		for (int color = Stone::BLACK; color <= Stone::WHITE; color++) {
			found |= checkLineImmutable(color, s, s, l, 1, 0);
			found |= checkLineImmutable(color, e, s, l, -1, 0);
			found |= checkLineImmutable(color, s, e, l, 1, 0);
			found |= checkLineImmutable(color, e, e, l, -1, 0);

			found |= checkLineImmutable(color, s, s, l, 0, 1);
			found |= checkLineImmutable(color, s, e, l, 0, -1);

			found |= checkLineImmutable(color, e, s, l, 0, 1);
			found |= checkLineImmutable(color, e, e, l, 0, -1);
		}
		if (!found) {
			break;
		}
	}

	for (int x = 0; x < BOARD_SIZE; x++) {
		for (int y = 0; y < BOARD_SIZE; y++) {
			checkWeight(x, y);
		}
	}

	for (int x = 0; x < BOARD_SIZE; x++) {
		for (int y = 0; y < BOARD_SIZE; y++) {
			Stone* stone = mBoard->mCells[x][y]->mStone;
			if (stone != NULL && !stone->mImmutable) {
				stone->mImmutable = mBoard->isImmutable(x, y);
			}
		}
	}
	for (int y = BOARD_SIZE - 1; y <= 0; y--) {
		for (int x = BOARD_SIZE - 1; x <= 0; x--) {
			Stone* stone = mBoard->mCells[x][y]->mStone;
			if (stone != NULL && !stone->mImmutable) {
				stone->mImmutable = mBoard->isImmutable(x, y);
			}
		}
	}
}

void Board::State::summary() {
	for (int x = 0; x < BOARD_SIZE; x++) {
		for (int y = 0; y < BOARD_SIZE; y++) {
			Stone* stone = mBoard->mCells[x][y]->mStone;
			if (stone != NULL) {
				if (stone->mColor == Stone::BLACK) {
					mCountBlack++;
					mPositionBlack += Board::POSITION_POINT[x][y];
					if (stone->mImmutable) {
						mImmutableBlack++;
					} else if (stone->mPotentialImmutable) {
						mPotentialImmutableBlack++;
					}
				} else {
					mCountWhite++;
					mPositionWhite += POSITION_POINT[x][y];
					if (stone->mImmutable) {
						mImmutableWhite++;
					} else if (stone->mPotentialImmutable) {
						mPotentialImmutableWhite++;
					}
				}
			}

		}
	}
}

void Board::State::checkWeight(int x, int y) {
	Stone* stone = mBoard->mCells[x][y]->mStone;
	if (stone == NULL || stone->mImmutable) {
		return;
	}
	SideState sides[8];

	sides[0] = checkSideWeight(x, y, -1, 0);
	sides[1] = checkSideWeight(x, y, 1, 0);
	sides[2] = checkSideWeight(x, y, 0, -1);
	sides[3] = checkSideWeight(x, y, 0, 1);
	sides[4] = checkSideWeight(x, y, -1, -1);
	sides[5] = checkSideWeight(x, y, 1, 1);
	sides[6] = checkSideWeight(x, y, -1, 1);
	sides[7] = checkSideWeight(x, y, 1, -1);
	if (sides[0].isFull() && sides[1].isFull() && sides[2].isFull() && sides[3].isFull() && sides[4].isFull() && sides[5].isFull() && sides[6].isFull()
			&& sides[7].isFull()) {
		stone->mImmutable = true;
		return;
	}

	bool immutable = (!sides[0].canExsistOther() || !sides[1].canPutOther()) && (!sides[1].canExsistOther() || !sides[0].canPutOther())
			&& (!sides[2].canExsistOther() || !sides[3].canPutOther()) && (!sides[3].canExsistOther() || !sides[2].canPutOther())
			&& (!sides[4].canExsistOther() || !sides[5].canPutOther()) && (!sides[5].canExsistOther() || !sides[4].canPutOther())
			&& (!sides[6].canExsistOther() || !sides[7].canPutOther()) && (!sides[7].canExsistOther() || !sides[6].canPutOther());

	if (immutable) {
		stone->mPotentialImmutable = true;
	} else {
		stone->mPotentialImmutable = false;
	}
}

Board::SideState Board::State::checkSideWeight(int x, int y, int stepX, int stepY) {
	SideState side;
	Stone* currentStone = mBoard->mCells[x][y]->mStone;
	for (int tx = x + stepX, ty = y + stepY; tx < BOARD_SIZE && ty < BOARD_SIZE && tx >= 0 && ty >= 0; tx += stepX, ty += stepY) {
		Stone* stone = mBoard->mCells[tx][ty]->mStone;
		if (stone == NULL) {
			side.mEmptyNum++;
		} else {
			if (side.mEmptyNum > 0) {
				break;
			} else if (currentStone->mColor != stone->mColor) {
				side.mNestNum++;
			}
			currentStone = stone;
		}
	}
	return side;
}

bool Board::State::checkLineImmutable(int color, int startX, int startY, int loop, int stepX, int stepY) {
	bool found = false;
	for (int x = startX, y = startY; loop > 0; x += stepX, y += stepY, loop--) {
		Cell* cell = mBoard->mCells[x][y];
		Stone* stone = cell->mStone;
		if (stone == NULL || stone->mColor != color) {
			break;
		} else if (mBoard->isImmutable(color, x, y)) {
			stone->mImmutable = true;
			found = true;
		}
	}
	return found;
}

bool Board::State::isCold() {
	return mCountBlack == 0 || mCountWhite == 0;
}

int Board::State::getPutableCount(int color) {
	return mPutableCellsCount[color];
}

int Board::State::getImmutableCount(int color) {
	return color == Stone::BLACK ? mImmutableBlack : mImmutableWhite;
}

int Board::State::getPotentialImmutableCount(int color) {
	return color == Stone::BLACK ? mPotentialImmutableBlack : mPotentialImmutableWhite;
}

int Board::State::getPositionRawPoint(int color) {
	return color == Stone::BLACK ? mPositionBlack : mPositionWhite;
}

int Board::State::getPoint(int color) {
	int won = 0;
	if (mBoard->mCompleted) {
		won = isWon(color) ? WON : -WON;
	} else if (isWideMarginWon(color)) {
		won = WON;
	} else if (isWideMarginWon(Stone::otherColor(color))) {
		won = -WON;
	} else if (isWideMarginPossibleWon(color)) {
		won = WON / 2;
	} else if (isWideMarginPossibleWon(Stone::otherColor(color))) {
		won = -WON / 2;
	}
	return won + (color == Stone::BLACK ? mPoint : -mPoint);
}

int Board::State::getPutablePoint(int color) {
	return color == Stone::BLACK ? mPutablePoint : -mPutablePoint;
}

int Board::State::getImmutablePoint(int color) {
	return color == Stone::BLACK ? mImmutablePoint : -mImmutablePoint;
}

int Board::State::getPotentialImmutablePoint(int color) {
	return color == Stone::BLACK ? mPotentialImmutablePoint : -mPotentialImmutablePoint;
}

int Board::State::getPositionPoint(int color) {
	return color == Stone::BLACK ? mPositionPoint : -mPositionPoint;
}

bool Board::State::isWon(int color) {
	return (mBoard->mCompleted && isAdvantage(color)) || (color == Stone::BLACK ? mCountWhite == 0 : mCountBlack == 0);
}

bool Board::State::isLoose(int color) {
	return (mBoard->mCompleted && !isAdvantage(color)) || (color == Stone::BLACK ? mCountBlack == 0 : mCountWhite == 0);
}

bool Board::State::isWideMarginWon(int color) {
	int half = (BOARD_SIZE * BOARD_SIZE) / 2;
	return (color == Stone::BLACK && mImmutableBlack > half) || (color == Stone::WHITE && mImmutableWhite > half);
}

bool Board::State::isWideMarginPossibleWon(int color) {
	int half = (BOARD_SIZE * BOARD_SIZE) / 2;
	return (color == Stone::BLACK && (mImmutableBlack + mPotentialImmutableBlack) > half)
			|| (color == Stone::WHITE && (mImmutableWhite + mPotentialImmutableWhite) > half);
}

bool Board::State::isAdvantage(int color) {
	return color == Stone::BLACK ? mCountBlack > mCountWhite : mCountBlack < mCountWhite;
}

string Board::State::toString() {
	stringstream sb;
	sb << "T:[";
	sb << mBoard->mTurnCount;
	sb << ":(";
	sb << mBoard->getStoneCount() << "/";
	sb << mBoard->getEmptyCellCount() << ")] BW[";
	sb << mCountBlack;
	sb << "/";
	sb << mCountWhite;
	sb << "] S:";
	sb << mBoard->mStoneNum;
	sb << ".";
	sb << mBoard->getEmptyCellCount();
	sb << " P:";
	sb << getPoint(Stone::BLACK);
	sb << "(PU:";
	sb << getPutablePoint(Stone::BLACK);
	sb << "[";
	sb << getPutableCount(Stone::BLACK);
	sb << "/";
	sb << getPutableCount(Stone::WHITE);
	sb << "],IM:";
	sb << getImmutablePoint(Stone::BLACK);
	sb << "[";
	sb << getImmutableCount(Stone::BLACK);
	sb << "/";
	sb << getImmutableCount(Stone::WHITE);
	sb << "],PIM:";
	sb << getPotentialImmutablePoint(Stone::BLACK);
	sb << "[";
	sb << getPotentialImmutableCount(Stone::BLACK);
	sb << "/";
	sb << getPotentialImmutableCount(Stone::WHITE);
	sb << "],PO:";
	sb << getPositionPoint(Stone::BLACK);
	sb << "[";
	sb << getPositionRawPoint(Stone::BLACK);
	sb << "/";
	sb << getPositionRawPoint(Stone::WHITE);
	sb << "]";
	if (mBoard->mCompleted) {
		sb << "\n";
		string winner = isAdvantage(Stone::BLACK) ? "Black" : "White";
		sb << winner;
		sb << " won!\n";
	}
	return sb.str();
}
